/*
 * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
 * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
 */
package com.innovarhealthcare.launcher;
import com.innovarhealthcare.launcher.interfaces.Progress;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import java.io.File;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.PrintWriter;
import java.io.FileWriter;

import java.net.URL;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 下载并解析 JNLP 文件，负责核心 JAR 与扩展 JAR 的获取和校验。
 * 
 * @author thait
 */
public class DownloadJNLP {
    private static final String LOG_FILE = "launcher-debug.log";
    private static final boolean DEBUG = false;
    private final File cacheFolder;
    private String host = "";
    private volatile boolean cancelled = false;

    public DownloadJNLP(String host, File cacheFolder) {
        if (host.endsWith("/")) {
            host = host.substring(0, host.length() - 1);
        }
        this.host = host;
        this.cacheFolder = cacheFolder;
    }

    public CodeBase handle(Progress progress) throws Exception {
        progress.updateProgressText("正在请求主 JNLP...");
        checkCancelled("处理开始");

        String jnlpUrl = host + "/" + "webstart.jnlp";

        log("🔍 正在获取主 JNLP: " + jnlpUrl);

        List<File> localJars = new ArrayList<>();
        String bridgeVersion = "未知";
        try {
            URL url = new URL(jnlpUrl);
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(url.openStream());

            List<String> coreJars = new ArrayList<>();

            // 提取核心 JAR
            NodeList jarList = doc.getElementsByTagName("jar");
            for (int i = 0; i < jarList.getLength(); i++) {
                checkCancelled("提取核心 JAR");
                Element jarElement = (Element) jarList.item(i);
                coreJars.add(jarElement.getAttribute("href"));
            }

            // 检测 Mirth 版本
            NodeList versionNodes = doc.getElementsByTagName("title");
            if (versionNodes.getLength() > 0) {
                bridgeVersion = versionNodes.item(0).getTextContent().replaceAll("[^0-9.]", "");
            }

            log("✅ 检测到 BridgeLink 版本: " + bridgeVersion);

            // 提取扩展 JNLP
            progress.updateProgressText("正在请求扩展 JNLP...");
            checkCancelled("提取扩展 JNLP");

            NodeList extensionNodes = doc.getElementsByTagName("extension");
            List<String> extensionJnlpUrls = new ArrayList<>();

            String baseUrl = jnlpUrl.replace("webstart.jnlp", "");
            for (int i = 0; i < extensionNodes.getLength(); i++) {
                Element extElement = (Element) extensionNodes.item(i);
                String extJnlpPath = extElement.getAttribute("href");
                String extJnlpUrl = baseUrl + extJnlpPath;
                extensionJnlpUrls.add(extJnlpUrl);
            }

            log("✅ 找到扩展 JNLPs: " + extensionJnlpUrls);

            // ✅ 解析扩展 JAR
            List<ExtensionInfo> listExtensions = new ArrayList<>();
            for (String extJnlpUrl : extensionJnlpUrls) {
                checkCancelled("解析扩展 JNLP");
                listExtensions.add(parseExtensionJnlp(extJnlpUrl));
            }

            // 下载 & 准备启动 Mirth
            localJars = download(jnlpUrl, coreJars, bridgeVersion, listExtensions, progress);
        } catch (Exception e) {
            log("❌ 处理时出错: " + e.getMessage());
            throw e;
        }

        // 构建类路径
        List<String> classpath = new ArrayList<>();
        for (File jar : localJars) {
            classpath.add(jar.getAbsolutePath());
        }

        return new CodeBase(classpath,"com.mirth.connect.client.ui.Mirth", host, bridgeVersion);
    }

    private ExtensionInfo parseExtensionJnlp(String extJnlpUrl) {
        log("🔍 正在获取扩展 JNLP: " + extJnlpUrl);

        try {
            URL url = new URL(extJnlpUrl);
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document doc = builder.parse(url.openStream());

            String extensionName = new File(new URL(extJnlpUrl).getPath()).getName().replace(".jnlp", "");
            Map<String, String> mapJars = new HashMap<>();

            NodeList jarList = doc.getElementsByTagName("jar");
            for (int i = 0; i < jarList.getLength(); i++) {
                Element jarElement = (Element) jarList.item(i);
                String jarPath = jarElement.getAttribute("href");
                String jarUrl = this.host + "/webstart/extensions/" + jarPath;
                mapJars.put(jarUrl, new File(jarPath).getName());
            }

            log("✅ 扩展 " + extensionName + " 的 JAR: " + mapJars.toString());

            return new ExtensionInfo(extensionName, mapJars);
        } catch (Exception e) {
            log("❌ 获取扩展 JNLP 出错: " + e.getMessage());
        }

        return new ExtensionInfo();
    }

    private List<File> download(String jnlpUrl, List<String> coreJars, String bridgeVersion,
            List<ExtensionInfo> listExtensions, Progress progress) throws Exception {
        log("🚀 开始下载 BridgeLink 版本: " + bridgeVersion);
        checkCancelled("下载开始");

        String baseUrl = jnlpUrl.substring(0, jnlpUrl.indexOf("/webstart") + 9);

        String path = bridgeVersion + "/core";
        File coreFolder = new File(cacheFolder, path);
        if (!coreFolder.exists()) {
            coreFolder.mkdirs();
        }

        List<File> localJars = new ArrayList<>();
        int numOfJars = coreJars.size() + listExtensions.size();
        int cntNum = 0;

        // ✅ 下载核心 JAR
        for (String jar : coreJars) {
            checkCancelled("下载核心 JAR");
            String correctedJarUrl = baseUrl + "/client-lib/" + new File(jar).getName();
            File localFile = new File(coreFolder, new File(jar).getName());

            progress.updateProgressText("正在下载核心 JAR " + jar + "...");
            log("⬇️ 下载核心 JAR: " + correctedJarUrl);

            cntNum += 1;

            if (!downloadFile(correctedJarUrl, localFile, null)) {
                log("❌ 警告: 无法下载核心 JAR: " + correctedJarUrl);
                continue;
            }

            localJars.add(localFile);
            progress.updateProgressBar((double) cntNum/numOfJars);
        }

        // ✅ 下载扩展 JAR
        for (ExtensionInfo jar : listExtensions) {
            checkCancelled("下载扩展 JAR");

            String extensionName = jar.getName();
            String extPath = bridgeVersion + "/extensions/" + extensionName;
            File extensionFolder = new File(cacheFolder, extPath);
            if (!extensionFolder.exists()) {
                extensionFolder.mkdirs();
            }

            Map<String, String> mapJars = jar.getMapJars();
            cntNum += 1;

            if (mapJars == null) {
                log("❌ 警告: 扩展 JAR 下载失败，mapJars 为空 - 扩展名: " + extensionName);
                continue;
            }

            for (Map.Entry<String, String> entry : mapJars.entrySet()) {
                checkCancelled("下载扩展 JAR 循环");
                String jarUrl = entry.getKey();
                String jarName = entry.getValue();
                File localFile = new File(extensionFolder, jarName);

                progress.updateProgressText("正在下载扩展 JAR " + jarName + "...");
                log("⬇️ 下载扩展 JAR: " + jarUrl + " -> " + localFile.getAbsolutePath());

                if (!downloadFile(jarUrl, localFile, null)) {
                    log("❌ 警告: 缺失扩展 JAR: " + jarUrl);
                    continue;
                }
                localJars.add(localFile);
            }

            progress.updateProgressBar((double) cntNum/numOfJars);
        }

        return localJars;
    }

    private boolean downloadFile(String urlStr, File destination, String expectedSha256) throws InterruptedException {
        if (Thread.interrupted() || cancelled){
            throw new InterruptedException("下载已取消");
        }

        try {
            if (destination.exists() && expectedSha256 != null) {
                String actualSha256 = calculateSha256(destination);
                if (expectedSha256.equalsIgnoreCase(actualSha256)) {
                    log("✅ 跳过已下载文件: " + destination.getName());
                    return true;
                }
            }

            log("⬇️ 正在下载: " + urlStr);
            URL url = new URL(urlStr);
            try (InputStream in = url.openStream();
                 FileOutputStream out = new FileOutputStream(destination)) {
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = in.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
            }

            if (expectedSha256 != null) {
                String actualSha256 = calculateSha256(destination);
                if (!expectedSha256.equalsIgnoreCase(actualSha256)) {
                    log("❌ 错误: SHA-256 校验失败 " + destination.getName());
                    destination.delete();
                    return false;
                }
            }

            log("✅ 保存成功: " + destination.getAbsolutePath());
            return true;
        } catch (Exception e) {
            log("❌ 下载文件失败: " + urlStr + " - " + e.getMessage());
            return false;
        }
    }

    private String calculateSha256(File file) throws IOException, NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        try (InputStream fis = new FileInputStream(file);
             DigestInputStream dis = new DigestInputStream(fis, digest)) {
            byte[] buffer = new byte[4096];
            while (dis.read(buffer) != -1) {
            }
        }
        byte[] hashBytes = digest.digest();
        StringBuilder hexString = new StringBuilder();
        for (byte b : hashBytes) {
            hexString.append(String.format("%02x", b));
        }
        return hexString.toString();
    }

    private void log(String message) {
        if(DEBUG){
            String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            String logMessage = "[" + timestamp + "] " + message;

            // 控制台打印
            System.out.println(logMessage);

            // 写入日志文件
            try (PrintWriter out = new PrintWriter(new FileWriter(LOG_FILE, true))) {
                out.println(logMessage);
            } catch (IOException e) {
                System.err.println("错误: 无法写入日志文件 - " + e.getMessage());
            }
        }
    }

    public void cancel() {
        cancelled = true;
    }

    private void checkCancelled(String context) throws InterruptedException {
        if (Thread.interrupted() || cancelled) {
            log("🚫 已取消: " + context);
            throw new InterruptedException("下载已取消");
        }
    }
}
